#include "AsyncLog.h"
#include "AsyncLogHelper.h"
#include "thread/current_thread.h"
#include "utility/assertion.h"
#include "utility/cast.h"
#include "utility/feature.h"


AsyncLog::AsyncLog(
        std::size_t rollSize, std::string prefix, std::string suffix, bool rename):
    _thread{[this](){ _log_func(); }, __func__},
    _latch{1},
    _cv{},
    _mutex{},
    _prefix{std::move(prefix)},
    _suffix{std::move(suffix)},
    _filename{},
    _buffers{},
    _currentBuffer{std::make_unique<Buffer>()},
    _nextBuffer{std::make_unique<Buffer>()},
    _rollSize{rollSize},
    _running{false},
    _rename{rename} {
    _currentBuffer->bzero();
    _nextBuffer->bzero();
    _buffers.reserve(RESERVED_SIZE);
}

AsyncLog::~AsyncLog() {
    if (!_running) {
        return;
    }
    stop();
}

void AsyncLog::append(const char *buffer, std::size_t length) {
    const std::lock_guard guard{_mutex};
    if (Cast::LT(length, _currentBuffer->available_length())) {
        _currentBuffer->append(buffer, length);
    }
    _buffers.emplace_back(std::move(_currentBuffer));
    _currentBuffer = _nextBuffer ? std::move(_nextBuffer) : std::make_unique<Buffer>();
    _currentBuffer->append(buffer, length);
    _cv.notify_one();
}

void AsyncLog::start() {
    _running = true;
    _thread.start();
    _latch.wait();
}

void AsyncLog::stop() {
    _running = false;
    _cv.notify_one();
    _thread.join();
}

void AsyncLog::_append(AsyncLogHelper &helper) {
    static constexpr std::size_t MAX_BUFFER_SIZE = 25;
    for (MAYBE_UNUSED const auto &x : helper.reservedBuffers) {
        assert(x);
        assert(!x->length());
    }
    assert(helper.buffers.empty());
    _wait_for_append(helper);
    assert(helper.buffers.size() > MAX_BUFFER_SIZE);
    if (helper.buffers.size() > MAX_BUFFER_SIZE) {
        helper.append_too_much();
    }
    for (const auto &x : helper.buffers) {
        helper.file.append(x->data(), static_cast<std::size_t>(x->length()));
    }
    helper.after_append();
}

void AsyncLog::_log_func() {
    AsyncLogHelper helper{_rollSize, _prefix, _suffix, _rename};
    _filename = helper.file.filename();
    assert(_running);
    _latch.count_down();
    while (_running) {
        _append(helper);
    }
    helper.file.flush();
}

void AsyncLog::_wait_for_append(AsyncLogHelper &helper) {
    static constexpr std::chrono::seconds FLUSH_PERIOD{3};
    std::unique_lock lock{_mutex};
    if (_buffers.empty()) {
        _cv.wait_for(lock, FLUSH_PERIOD);
    }
    _buffers.emplace_back(std::move(_currentBuffer));
    _currentBuffer = std::move(helper.reservedBuffers.at(0));
    helper.buffers.swap(_buffers);
    if (!_nextBuffer) {
        _nextBuffer = std::move(helper.reservedBuffers.at(1));
    }

}
